package com.such.kit.database.dialect;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.such.kit.Logger;
import com.such.kit.bean.BeanCommons;
import com.such.kit.database.exception.DatabaseException;
import com.such.kit.placeholder.PlaceholderCommons;
import com.such.kit.resource.support.ReadResource;
import com.such.kit.string.StringCommons;
import com.such.kit.validate.ValidateCommons;
import com.such.kit.validate.ValidateDataType;

/**
 * @作者 SUCH
 * @日期 2017-8-25 下午4:41:58
 * @描述 数据库函数工厂
 * <p>
 * resourceKey() 为默认资源键，当 resourceKey(参数数量) 未找到值时则使用 resourceKey() 再次查找<br>
 * <br>
 * 格式 1：resourceKey(参数数量)={template: "函数模板", separator: "参数分隔符"}<br>
 * 函数模板不可缺失，可以在函数模板中使用 #[key] 格式占位符<br>
 * 1. #[0...]：取  params.get(0...) 替换占位符<br>
 * 2. #[...N]：按照参数分隔符对参数进行处理后替换占位符<br>
 * 程序会对参数列表进行动态规划：<br>
 * 假设有参数列表 ['A', 'B', 'C', 'D']，separator 为 ||<br>
 * 1. 优先处理 #[0...]，如：#[0] > 'A' | #[1] > 'B'<br>
 * 2. 将剩下的参数按照 #[...N] 处理，如：#[...N] > 'C' || 'D'<br>
 * <br>
 * 格式 2：resourceKey(参数数量)={className: "长类名", method: "方法名"}<br>
 * 格式 2 为一种补充方案，用于处理复杂的函数实现，程序会优先使用格式 1，当未找到 template 时则按照格式 2 处理<br>
 * 程序会掉用 java 类中公开的静态方法，方法返回值为 String<br>
 * </p>
 * <p>
 * 警告：<br>
 * 程序不会判断参数列表中是数字、字符串、字段等，需要自行在传值时或在模板定义时自行处理<br>
 * 在对函数进行包装时遵循函数目标不作处理，函数参数类在模板中进行处理<br>
 * 如 to_date(field, format) 在模板中定义为 to_date(#[0], '#[1]')，field 为函数目标不作处理（无法确定传入类型），format 为函数参数进行处理（函数参数为确认数据类型）<br>
 * </p>
 */
public class FuncFactory {

	private static final String FUNC_RENDERER_TO_N = "...N";
	private static final String FUNC_RENDERER_PARAMS_EMPTY_FILL = "PARAMS_EMPTY_FILL";

	private String resourceName;

	public FuncFactory(String resourceName) {
		this.resourceName = resourceName;
	}

	/**
	 * <p>方法名：stringToNumber</p>
	 * <p>描述：字符串转换为数字</p>
	 * <p>逻辑：将 field 转换为字符串</p>
	 * @param field 字段
	 * @return 函数
	 */
	public String stringToNumber(String field) {
		return funcRenderer("stringToNumber", field);
	}

	/**
	 * <p>方法名：numberToString</p>
	 * <p>描述：数字转换为字符串</p>
	 * <p>逻辑：将 field 转换为字符串</p>
	 * @param field 字段
	 * @return 函数
	 */
	public String numberToString(String field) {
		return funcRenderer("numberToString", field);
	}

	/**
	 * <p>方法名：stringToDate</p>
	 * <p>描述：字符串转换为日期</p>
	 * <p>逻辑：将 field 按 format 格式转换为日期</p>
	 * @param field 字段
	 * @param format 日期格式
	 * @return 函数
	 */
	public String stringToDate(String field, String format) {
		return funcRenderer("stringToDate", field, format);
	}

	/**
	 * <p>方法名：dateToString</p>
	 * <p>描述：日期转换为字符串</p>
	 * <p>逻辑：将 field 按 format 格式转换为字符串</p>
	 * @param field 字段
	 * @param format 日期格式
	 * @return 函数
	 */
	public String dateToString(String field, String format) {
		return funcRenderer("dateToString", field, format);
	}

	/**
	 * <p>方法名：date</p>
	 * <p>描述：获取当前日期</p>
	 * @return 函数
	 */
	public String date() {
		return funcRenderer("date");
	}

	/**
	 * <p>方法名：datetime</p>
	 * <p>描述：获取当前日期时间</p>
	 * @return 函数
	 */
	public String datetime() {
		return funcRenderer("datetime");
	}

	/**
	 * <p>方法名：datetimestamp</p>
	 * <p>描述：获取当前时间戳</p>
	 * @return 函数
	 */
	public String datetimestamp() {
		return funcRenderer("datetimestamp");
	}

	/**
	 * <p>方法名：year</p>
	 * <p>描述：取指定日期年</p>
	 * <p>
	 * 逻辑：<br>
	 * 1. field、format 都不为空：将 field 按 format 格式转换为日期后取其年<br>
	 * 2. field 不为空：取日期 field 年<br>
	 * 3. field、format 都为空：取当前日期年<br>
	 * </p>
	 * @param field 字段
	 * @param format 日期格式
	 * @return 函数
	 */
	public String year(String field, String format) {
		if (ValidateCommons.isNotEmpty(field) && ValidateCommons.isNotEmpty(format)) {
			return funcRenderer("year", stringToDate(field, format));
		} else if (ValidateCommons.isNotEmpty(field)) {
			return funcRenderer("year", field);
		} else {
			return funcRenderer("year");
		}
	}

	/**
	 * <p>方法名：month</p>
	 * <p>描述：取指定日期月</p>
	 * <p>
	 * 逻辑：<br>
	 * 1. field、format 都不为空：将 field 按 format 格式转换为日期后取其月<br>
	 * 2. field 不为空：取日期 field 月<br>
	 * 3. field、format 都为空：取当前日期月<br>
	 * </p>
	 * @param field 字段
	 * @param format 日期格式
	 * @return 函数
	 */
	public String month(String field, String format) {
		if (ValidateCommons.isNotEmpty(field) && ValidateCommons.isNotEmpty(format)) {
			return funcRenderer("month", stringToDate(field, format));
		} else if (ValidateCommons.isNotEmpty(field)) {
			return funcRenderer("month", field);
		} else {
			return funcRenderer("month");
		}
	}

	/**
	 * <p>方法名：day</p>
	 * <p>描述：取指定日期日</p>
	 * <p>
	 * 逻辑：<br>
	 * 1. field、format 都不为空：将 field 按 format 格式转换为日期后取其日<br>
	 * 2. field 不为空：取日期 field 日<br>
	 * 3. field、format 都为空：取当前日期日<br>
	 * </p>
	 * @param field 字段
	 * @param format 日期格式
	 * @return 函数
	 */
	public String day(String field, String format) {
		if (ValidateCommons.isNotEmpty(field) && ValidateCommons.isNotEmpty(format)) {
			return funcRenderer("day", stringToDate(field, format));
		} else if (ValidateCommons.isNotEmpty(field)) {
			return funcRenderer("day", field);
		} else {
			return funcRenderer("day");
		}
	}

	/**
	 * <p>方法名：hour</p>
	 * <p>描述：取指定时间时</p>
	 * <p>
	 * 逻辑：<br>
	 * 1. field、format 都不为空：将 field 按 format 格式转换为时间后取其时<br>
	 * 2. field 不为空：取时间 field 时<br>
	 * 3. field、format 都为空：取当前时间时<br>
	 * </p>
	 * @param field 字段
	 * @param format 日期格式
	 * @return 函数
	 */
	public String hour(String field, String format) {
		if (ValidateCommons.isNotEmpty(field) && ValidateCommons.isNotEmpty(format)) {
			return funcRenderer("hour", stringToDate(field, format));
		} else if (ValidateCommons.isNotEmpty(field)) {
			return funcRenderer("hour", field);
		} else {
			return funcRenderer("hour");
		}
	}

	/**
	 * <p>方法名：minute</p>
	 * <p>描述：取指定时间分</p>
	 * <p>
	 * 逻辑：<br>
	 * 1. field、format 都不为空：将 field 按 format 格式转换为时间后取其分<br>
	 * 2. field 不为空：取时间 field 分<br>
	 * 3. field、format 都为空：取当前时间分<br>
	 * </p>
	 * @param field 字段
	 * @param format 日期格式
	 * @return 函数
	 */
	public String minute(String field, String format) {
		if (ValidateCommons.isNotEmpty(field) && ValidateCommons.isNotEmpty(format)) {
			return funcRenderer("minute", stringToDate(field, format));
		} else if (ValidateCommons.isNotEmpty(field)) {
			return funcRenderer("minute", field);
		} else {
			return funcRenderer("minute");
		}
	}

	/**
	 * <p>方法名：second</p>
	 * <p>描述：取指定时间秒</p>
	 * <p>
	 * 逻辑：<br>
	 * 1. field、format 都不为空：将 field 按 format 格式转换为时间后取其秒<br>
	 * 2. field 不为空：取时间 field 秒<br>
	 * 3. field、format 都为空：取当前时间秒<br>
	 * </p>
	 * @param field 字段
	 * @param format 日期格式
	 * @return 函数
	 */
	public String second(String field, String format) {
		if (ValidateCommons.isNotEmpty(field) && ValidateCommons.isNotEmpty(format)) {
			return funcRenderer("second", stringToDate(field, format));
		} else if (ValidateCommons.isNotEmpty(field)) {
			return funcRenderer("second", field);
		} else {
			return funcRenderer("second");
		}
	}

	/**
	 * <p>方法名：millisecond</p>
	 * <p>描述：取指定时间毫秒</p>
	 * <p>
	 * 逻辑：<br>
	 * 1. field、format 都不为空：将 field 按 format 格式转换为时间后取其毫秒<br>
	 * 2. field 不为空：取时间 field 毫秒<br>
	 * 3. field、format 都为空：取当前时间毫秒<br>
	 * </p>
	 * @param field 字段
	 * @param format 日期格式
	 * @return 函数
	 */
	public String millisecond(String field, String format) {
		if (ValidateCommons.isNotEmpty(field) && ValidateCommons.isNotEmpty(format)) {
			return funcRenderer("millisecond", stringToDate(field, format));
		} else if (ValidateCommons.isNotEmpty(field)) {
			return funcRenderer("millisecond", field);
		} else {
			return funcRenderer("millisecond");
		}
	}

	/**
	 * <p>方法名：sum</p>
	 * <p>描述：数字求和</p>
	 * <p>逻辑：对 field 求和</p>
	 * @param field 字段
	 * @return 函数
	 */
	public String sum(String field) {
		return funcRenderer("sum", field);
	}

	/**
	 * <p>方法名：avg</p>
	 * <p>描述：数字求平均值</p>
	 * <p>逻辑：对 field 求平均值</p>
	 * @param field 字段
	 * @return 函数
	 */
	public String avg(String field) {
		return funcRenderer("avg", field);
	}

	/**
	 * <p>方法名：max</p>
	 * <p>描述：数字求最大值</p>
	 * <p>逻辑：对 field 求最大值</p>
	 * @param field 字段
	 * @return 函数
	 */
	public String max(String field) {
		return funcRenderer("max", field);
	}

	/**
	 * <p>方法名：min</p>
	 * <p>描述：数字求最小值</p>
	 * <p>逻辑：对 field 求最小值</p>
	 * @param field 字段
	 * @return 函数
	 */
	public String min(String field) {
		return funcRenderer("min", field);
	}

	/**
	 * <p>方法名：abs</p>
	 * <p>描述：数字求绝对值</p>
	 * <p>逻辑：对 field 求绝对值</p>
	 * @param field 字段
	 * @return 函数
	 */
	public String abs(String field) {
		return funcRenderer("abs", field);
	}

	/**
	 * <p>方法名：round</p>
	 * <p>描述：数字四舍五入</p>
	 * <p>逻辑：对 field 进行四舍五入并保留 scale 位小数</p>
	 * @param field 字段
	 * @param scale 精确小数位
	 * @return 函数
	 */
	public String round(String field, int scale) {
		return funcRenderer("round", field, String.valueOf(scale));
	}

	/**
	 * <p>方法名：cut</p>
	 * <p>描述：数字截取</p>
	 * <p>逻辑：对 field 截取保留 scale 位小数</p>
	 * @param field 字段
	 * @param scale 精确小数位
	 * @return 函数
	 */
	public String cut(String field, int scale) {
		return funcRenderer("cut", field, String.valueOf(scale));
	}

	/**
	 * <p>方法名：mod</p>
	 * <p>描述：数字求模</p>
	 * <p>逻辑：numerator 除以 denominator 的余数（未除净）</p>
	 * @param numerator 分子
	 * @param denominator 分母
	 * @return 函数
	 */
	public String mod(String numerator, String denominator) {
		return funcRenderer("mod", numerator, denominator);
	}

	/**
	 * <p>方法名：length</p>
	 * <p>描述：字符串长度计算</p>
	 * <p>逻辑：计算 field 长度</p>
	 * @param field 字段
	 * @return 函数
	 */
	public String length(String field) {
		return funcRenderer("length", field);
	}

	/**
	 * <p>方法名：ltrim</p>
	 * <p>描述：去左空格</p>
	 * <p>逻辑：去除 field 左边空格</p>
	 * @param field 字段
	 * @return 函数
	 */
	public String ltrim(String field) {
		return funcRenderer("ltrim", field);
	}

	/**
	 * <p>方法名：rtrim</p>
	 * <p>描述：去右空格</p>
	 * <p>逻辑：去除 field 右边空格</p>
	 * @param field 字段
	 * @return 函数
	 */
	public String rtrim(String field) {
		return funcRenderer("rtrim", field);
	}

	/**
	 * <p>方法名：trim</p>
	 * <p>描述：去左右空格</p>
	 * <p>逻辑：去除 field 左右二边空格</p>
	 * @param field 字段
	 * @return 函数
	 */
	public String trim(String field) {
		return funcRenderer("trim", field);
	}

	/**
	 * <p>方法名：lpad</p>
	 * <p>描述：在左填充字符到指定长度</p>
	 * <p>逻辑：在 field 左填充 length 个字符 character</p>
	 * @param field 字段
	 * @param character 填充字符
	 * @param length 长度
	 * @return 函数
	 */
	public String lpad(String field, String character, int length) {
		return funcRenderer("lpad", field, character, String.valueOf(length));
	}

	/**
	 * <p>方法名：rpad</p>
	 * <p>描述：在右填充字符到指定长度</p>
	 * <p>逻辑：在 field 右填充字符 character 直到 field 的长度等于 length</p>
	 * @param field 字段
	 * @param character 填充字符
	 * @param length 长度
	 * @return 函数
	 */
	public String rpad(String field, String character, int length) {
		return funcRenderer("rpad", field, character, String.valueOf(length));
	}

	/**
	 * <p>方法名：substring</p>
	 * <p>描述：字符串截取</p>
	 * <p>逻辑：将 field 从 startIndex 开始，截取 length 个字符</p>
	 * @param field 待处理字段
	 * @param startIndex 开始位置（下标从 0 开始）
	 * @param length 截取长度（部分中英文）
	 * @return 函数
	 */
	public String substring(String field, int startIndex, int length) {
		return funcRenderer("substring", field, String.valueOf(startIndex), String.valueOf(length));
	}

	/**
	 * <p>方法名：concat</p>
	 * <p>描述：字符串连接</p>
	 * <p>逻辑：将参数列表中的字段连接在一起</p>
	 * @param params 参数列表
	 * @return 函数
	 */
	public String concat(String... params) {
		if (ValidateCommons.isEmpty(params)) {
			throw new DatabaseException("参数列表为空，与函数 [concat] 不匹配");
		} else if (params.length == 1) {
			return params[0];
		} else {
			return funcRenderer("concat", params);
		}
	}

	/**
	 * <p>方法名：upper</p>
	 * <p>描述：转换为大写</p>
	 * <p>逻辑：将 field 转换为大写</p>
	 * @param field 字段
	 * @return 函数
	 */
	public String upper(String field) {
		return funcRenderer("upper", field);
	}
	
	/**
	 * <p>方法名：lower</p>
	 * <p>描述：转换为小写</p>
	 * <p>逻辑：将 field 转换为小写</p>
	 * @param field 字段
	 * @return 函数
	 */
	public String lower(String field) {
		return funcRenderer("lower", field);
	}

	/**
	 * <p>方法名：replace</p>
	 * <p>描述：字符串替换</p>
	 * <p>逻辑：从 field 中查找 target 替换为 replacement</p>
	 * @param field 待处理字段
	 * @param target 查找目标
	 * @param replacement 替换为
	 * @return 函数
	 */
	public String replace(String field, String target, String replacement) {
		return funcRenderer("replace", field, target, replacement);
	}

	/**
	 * <p>方法名：indexOf</p>
	 * <p>描述：字符串查找</p>
	 * <p>逻辑：从 field 开头位置开始查找 target 的位置</p>
	 * @param field 待处理字段
	 * @param target 查找目标
	 * @return 函数
	 */
	public String indexOf(String field, String target) {
		return funcRenderer("indexOf", field, target);
	}

	/**
	 * <p>方法名：lastIndexOf</p>
	 * <p>描述：字符串查找</p>
	 * <p>逻辑：从 field 末尾位置开始查找 target 的位置</p>
	 * @param field 待处理字段
	 * @param target 查找目标
	 * @return 函数
	 */
	public String lastIndexOf(String field, String target) {
		return funcRenderer("lastIndexOf", field, target);
	}

	/**
	 * <p>方法名：isEmpty</p>
	 * <p>描述：是否为空</p>
	 * <p>逻辑：判断 field 是否为空，是则返回 true，否则返回 false</p>
	 * @param field 字段
	 * @return 函数
	 */
	public String isEmpty(String field) {
		return funcRenderer("isEmpty", field);
	}

	/**
	 * <p>方法名：isNotEmpty</p>
	 * <p>描述：是否不为空</p>
	 * <p>逻辑：判断 field 是否不为空，是则返回 true，否则返回 false</p>
	 * @param field 字段
	 * @return 函数
	 */
	public String isNotEmpty(String field) {
		return funcRenderer("isNotEmpty", field);
	}

	/**
	 * <p>方法名：nvl</p>
	 * <p>描述：空值处理</p>
	 * <p>逻辑：field 为空返回 backupField</p>
	 * @param field 字段
	 * @param backupField 后备字段
	 * @return 函数
	 */
	public String nvl(String field, String backupField) {
		return funcRenderer("nvl", field, backupField);
	}

	/**
	 * <p>方法名：ternary</p>
	 * <p>描述：三元表达式</p>
	 * <p>逻辑：condition 成立返回 trueVal，否则返回 falseVal</p>
	 * @param condition 条件
	 * @param trueVal 条件成立返回值
	 * @param falseVal 条件不成立返回值
	 * @return 函数
	 */
	public String ternary(String condition, String trueVal, String falseVal) {
		return funcRenderer("ternary", condition, trueVal, falseVal);
	}

	/**
	 * <p>方法名：funcRenderer</p>
	 * <p>描述：函数渲染器</p>
	 * @param resourceKey 资源 key
	 * @param params 参数列表
	 * @return 函数
	 */
	public String funcRenderer(String resourceKey, String... params) {
		int paramsLen = 0;
		if (ValidateCommons.isNotEmpty(params)) {
			paramsLen = params.length;
		}
		String funcKey = StringCommons.merger(resourceKey, "(", paramsLen, ")");
		String funcValue = ReadResource.getValue(this.resourceName, funcKey);
		if (ValidateCommons.isEmpty(funcValue)) {
			funcKey = StringCommons.merger(resourceKey, "()");
			funcValue = ReadResource.getValue(this.resourceName, funcKey);
		}
		if (ValidateCommons.isEmpty(funcValue)) {
			throw new DatabaseException(StringCommons.merger("未在 [", this.resourceName, "] 中找到 [", resourceKey, "] 对应的函数模板"));
		}
		Map<String, String> funcObject = JSON.parseObject(funcValue, new TypeReference<Map<String, String>>(){}.getType());
		String template = funcObject.get("template");
		String separator = funcObject.get("separator");
		String className = funcObject.get("className");
		String method = funcObject.get("method");
		if (ValidateCommons.isNotEmpty(template)) {
			List<String> innerKeys = PlaceholderCommons.getInnerKeys(template);
			Map<String, String> paramMap = new HashMap<String, String>();
			if (ValidateCommons.isNotEmpty(innerKeys)) {
				for (String innerKey : innerKeys) {
					if (ValidateDataType.validateInteger(innerKey)) {
						int index = Integer.valueOf(innerKey);
						String param = params[index];
						if (!FUNC_RENDERER_PARAMS_EMPTY_FILL.equals(param)) {
							// 使用 FUNC_RENDERER_PARAMS_EMPTY_FILL 替换被取出的数据，保证 params 长度不变
							params[index] = FUNC_RENDERER_PARAMS_EMPTY_FILL;
							paramMap.put(innerKey, param);
						}
					}
				}
			}
			if (ValidateCommons.isNotEmpty(separator)) {
				StringBuffer toN = new StringBuffer();
				for (int i = 0; i < paramsLen; i++) {
					String param = params[i];
					if (!FUNC_RENDERER_PARAMS_EMPTY_FILL.equals(param)) {
						if (toN.length() > 0) {
							toN.append(separator);
						}
						toN.append(param);
					}
				}
				paramMap.put(FUNC_RENDERER_TO_N, toN.toString());
			}
			return PlaceholderCommons.replacePlaceholder(template, paramMap);
		} else {
			try {
				Class<?>[] types = new Class<?>[paramsLen];
				Object[] args = new Object[paramsLen];
				for (int i = 0; i < paramsLen; i++) {
					types[i] = String.class;
					args[i] = params[i];
				}
				return BeanCommons.callMethod(BeanCommons.getClass(className), method, types, args);
			} catch (Exception e) {
				Logger.error(getClass(), StringCommons.merger("执行 [", className, "] 中方法 [", method, "] 失败"), e);
				throw new DatabaseException(e);
			}
		}
	}

}
